using System;
using System.Threading;
using System.Reflection;

using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;

using System.Security;
using System.Security.Principal;

namespace CodePlex.Diagnostics.Providers
{
    /// <summary>
    /// Using the <see cref="M:CodePlex.Diagnostics.Providers.ExceptionProvider.Publish"/> method, exceptions can be published using one or more exception providers.
    /// </summary>
    public static class ExceptionProvider
    {
        #region public static Guid Publish(Exception ex, IIdentity identity)

        /// <summary>
        /// Publishes the specified <see cref="T:System.Exception"/> using the configured exception provider.
        /// </summary>
        /// <param name="ex">The <see cref="T:System.Exception"/> to be published.</param>
        /// <param name="identity">The <see cref="T:System.Security.Principal.IIdentity"/> to associate with this exception.</param>
        /// <returns>A <see cref="T:System.Guid"/> which can be used by users to reference the <see cref="T:System.Exception"/> that occurred.</returns>
        [SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily")]
        public static Guid Publish(Exception ex, IIdentity identity)
        {
            if(ex == null)
            {
                throw new ArgumentNullException("ex");
            }

            if(identity == null)
            {
                throw new ArgumentNullException("identity");
            }

            // search within the supplied Exception for an instance of the PublishedException class, if found retrieve the publicationId.
            Guid? publicationId = GetPublicationId(ex);

            // ignore exceptions that are already published and return the existing publication Id.
            if (publicationId.HasValue)
            {
                return publicationId.Value;
            }

            // establish an instance of the currently configured exception provider.
            ExceptionProviderBase provider = ExceptionProviderBase.CreateInstance();

            // create a new thread to publish the exception.
            Thread _thread = new Thread(new ParameterizedThreadStart(provider.Publish));

            // create a new instance of the ExceptionContext class with surrounding information regarding the exception.
            ExceptionContext context = new ExceptionContext(ex,
                                                            identity,
                                                            Assembly.GetCallingAssembly(),
                                                            Process.GetCurrentProcess(),
                                                            Thread.CurrentThread,
                                                            AppDomain.CurrentDomain);

            // publish the exception using the configured exception provider.
            _thread.Start(context);

            // block the calling thread until the thread terminates.
            _thread.Join();

            return context.Id;
        }

        #endregion

        #region private static Guid? GetPublicationId(Exception ex)

        /// <summary>
        /// Recursively searches within the supplied <see cref="T:System.Exception"/> for an instance of the <see cref="T:PublishedException"/> class.
        /// </summary>
        /// <param name="ex">The <see cref="T:System.Exception"/> to search within for an instance of the <see cref="T:PublishedException"/> class.</param>
        /// <returns>The publication Id of the <see cref="T:PublishedException"/> or <c>null</c> if not found.</returns>
        private static Guid? GetPublicationId(Exception ex)
        {
            PublishedException publishedException = ex as PublishedException;

            if (publishedException != null)
            {
                // return the publication Id for the given PublishedException.
                return publishedException.PublicationId;
            }
            else if (ex.InnerException != null)
            {
                // recursively search for an instance of the PublishedException class.
                return GetPublicationId(ex.InnerException);
            }
            else
            {
                // no instance of the PublishedException class was found, therefore return null.
                return null;
            }
        }

        #endregion
    }
}
